#include "video_svr.h"

CVideo_svr::CVideo_svr(LPST_ENV_T pEnv, int nCh)
: m_pEnv(pEnv)
, m_nChId(nCh)
, m_thread_id(NULL)
, m_pOsd(NULL)
{
	LOG4CPLUS_DEBUG(log_dsw, "Create channel [ " << nCh << " ] video server!");
	int nChId = nCh;
	void* pParam = (void*)(&nChId);
	pEnv->pOnNotifyFunc(PMSG_GET_VIDEOINFO, pParam);
	m_pChParam = (LPST_CH_PARAM)pParam;

	pthread_mutex_init(&m_alarm_list.nLock, NULL);

	StartMainSubStream(nCh);
}

CVideo_svr::~CVideo_svr()
{
	StopMainSunStream();

	while(!m_alarm_list.nAlarmList.empty()){
		LPST_ALARM_MSG_T pNode = m_alarm_list.nAlarmList.front();
		m_alarm_list.nAlarmList.pop_front();
		delete pNode;
	}
	pthread_mutex_destroy(&m_alarm_list.nLock);
	
	LOG4CPLUS_DEBUG(log_dsw, "Destroy channel [ " << m_nChId << " ] video server!");
}

void CVideo_svr::StartMainSubStream(int nCh)
{
	pthread_mutex_init(&m_video_mem[MAIN_STREAM].lock, NULL);
	pthread_mutex_init(&m_video_mem[SUB_STREAM].lock, NULL);
	m_video_mem[MAIN_STREAM].seq = m_video_mem[SUB_STREAM].seq = 0;
	
	DECLARE_ATTR(cap_attr, gm_cap_attr_t);
	DECLARE_ATTR(h264e_attr, gm_h264e_attr_t);
	DECLARE_ATTR(cap_3di_attr, gm_3di_attr_t);

	m_groupfd = gm_new_groupfd();
	m_capture_object = gm_new_obj(GM_CAP_OBJECT);
	m_main_enc_object = gm_new_obj(GM_ENCODER_OBJECT);
	m_sub_enc_object = gm_new_obj(GM_ENCODER_OBJECT);

	/*** Init mainstream */
	cap_attr.cap_vch = nCh;
	cap_attr.path = GM_MAINSUBSTREAM;
	cap_attr.enable_mv_data = 0;
	gm_set_attr(m_capture_object, &cap_attr);

	cap_3di_attr.deinterlace = 1;
	cap_3di_attr.denoise = 1;
	gm_set_attr(m_capture_object, &cap_3di_attr);

	h264e_attr.dim.width = MAKE_V_WIDTH(m_pChParam->vtype, m_pChParam->bs[MAIN_STREAM].size);//pEnv->gm_system.cap[nCh].dim.width;
	h264e_attr.dim.height = MAKE_V_HEIGHT(m_pChParam->vtype, m_pChParam->bs[MAIN_STREAM].size);//pEnv->gm_system.cap[nCh].dim.height;
	h264e_attr.frame_info.framerate = m_pChParam->bs[MAIN_STREAM].fps;//pEnv->gm_system.cap[nCh].framerate;
	h264e_attr.ratectl.mode = GM_CBR;
	h264e_attr.ratectl.gop = m_pChParam->bs[MAIN_STREAM].gop;//60;
	h264e_attr.ratectl.bitrate = m_pChParam->bs[MAIN_STREAM].bps;//2048;  // 2Mbps
	h264e_attr.b_frame_num = 0; // B-frames per GOP (H.264 high profile)
	h264e_attr.enable_mv_data = 0;  // disable H.264 motion data output
	gm_set_attr(m_main_enc_object, &h264e_attr);

	m_main_bindfd = gm_bind(m_groupfd, m_capture_object, m_main_enc_object);

	/*** Init substream */
	h264e_attr.dim.width = MAKE_V_WIDTH(m_pChParam->vtype, m_pChParam->bs[SUB_STREAM].size);//pEnv->gm_system.cap[nCh].dim.width / 2;
	h264e_attr.dim.height = MAKE_V_HEIGHT(m_pChParam->vtype, m_pChParam->bs[SUB_STREAM].size);//pEnv->gm_system.cap[nCh].dim.height / 2;
	h264e_attr.frame_info.framerate = m_pChParam->bs[SUB_STREAM].fps;//pEnv->gm_system.cap[nCh].framerate;
	h264e_attr.ratectl.bitrate = m_pChParam->bs[SUB_STREAM].gop;//512;
	gm_set_attr(m_sub_enc_object, &h264e_attr);

	m_sub_bindfd = gm_bind(m_groupfd, m_capture_object, m_sub_enc_object);
	
	if (gm_apply(m_groupfd) < 0){
		LOG4CPLUS_ERROR(log_dsw, "gm_apply fail, AP procedure something wrong!");
	}

	m_pOsd = new CDswosd(m_pEnv, m_capture_object, nCh);
	
	pthread_create(&m_thread_id, NULL, CVideo_svr::cbThreadFunc, (void *)this);
}

void CVideo_svr::StopMainSunStream()
{
	if (m_thread_id)
		pthread_join(m_thread_id, NULL);
	if (m_pOsd)
		delete m_pOsd;
	gm_unbind(m_main_bindfd);
	gm_unbind(m_sub_bindfd);
	gm_apply(m_groupfd);
	gm_delete_obj(m_main_enc_object);
	gm_delete_obj(m_sub_enc_object);
	gm_delete_obj(m_capture_object);
	gm_delete_groupfd(m_groupfd);

	while(!m_video_mem[MAIN_STREAM].vlist.empty()){
		LPST_VIDEO_FRAME_T pNode = m_video_mem[MAIN_STREAM].vlist.front();
		m_video_mem[MAIN_STREAM].vlist.pop_front();
		if (pNode->pFrame != NULL)
			delete pNode->pFrame;
		delete pNode;
	}
	while(!m_video_mem[SUB_STREAM].vlist.empty()){
		LPST_VIDEO_FRAME_T pNode = m_video_mem[SUB_STREAM].vlist.front();
		m_video_mem[SUB_STREAM].vlist.pop_front();
		if (pNode->pFrame != NULL)
			delete pNode->pFrame;
		delete pNode;
	}
	pthread_mutex_destroy(&m_video_mem[MAIN_STREAM].lock);
	pthread_mutex_destroy(&m_video_mem[SUB_STREAM].lock);
}

void CVideo_svr::EncThreadFunc()
{
	LOG4CPLUS_DEBUG(log_dsw, "Start Ch[ " << m_nChId << " ] Video encode thread!");
	char *bitstream_data[MAX_V_BITSTREAM_NUM];
	int bitstream_datalen[MAX_V_BITSTREAM_NUM];
	gm_pollfd_t poll_fds[MAX_V_BITSTREAM_NUM];
	gm_enc_multi_bitstream_t multi_bs[MAX_V_BITSTREAM_NUM];
	int i, ret;

	pthread_t nCapturePicThread;

	pthread_create(&nCapturePicThread, NULL, CVideo_svr::cbCapturePICThreadFunc, (void *)this);
	
	for (i = 0; i < MAX_V_BITSTREAM_NUM; i++){
		bitstream_data[i] = NULL;
		bitstream_datalen[i] = 0;
	}

	memset(poll_fds, 0, sizeof(poll_fds));

	/*** Capture mainstream stream */
	poll_fds[MAIN_STREAM].bindfd = m_main_bindfd;
	poll_fds[MAIN_STREAM].event = GM_POLL_READ;
	/*** Capture another path substream */
	poll_fds[SUB_STREAM].bindfd = m_sub_bindfd;
	poll_fds[SUB_STREAM].event = GM_POLL_READ;

	while(!m_pEnv->bExit){
		ret = gm_poll(poll_fds, MAX_V_BITSTREAM_NUM, 500);
		if (ret == GM_TIMEOUT) {
			LOG4CPLUS_WARN(log_dsw, "Poll video timeout!!");
			continue;
		}
		memset(multi_bs, 0, sizeof(multi_bs));
		for (i = 0; i < MAX_V_BITSTREAM_NUM; i++) {
			if (poll_fds[i].revent.event != GM_POLL_READ)
				continue;
			if (poll_fds[i].revent.bs_len > V_BITSTREAM_LEN) {
				LOG4CPLUS_ERROR(log_dsw, "bitstream buffer length is not enough!" << poll_fds[i].revent.bs_len << ", " << V_BITSTREAM_LEN);
				continue;
			}
			multi_bs[i].bindfd = poll_fds[i].bindfd;
			if (poll_fds[i].revent.bs_len > bitstream_datalen[i]){
				if (bitstream_data[i] != NULL)
					delete bitstream_data[i];
				bitstream_data[i] = new char[poll_fds[i].revent.bs_len];
				bitstream_datalen[i] = poll_fds[i].revent.bs_len;
			}
			multi_bs[i].bs.bs_buf = bitstream_data[i];
			multi_bs[i].bs.bs_buf_len = bitstream_datalen[i];//poll_fds[i].revent.bs_len;
			multi_bs[i].bs.mv_buf = 0;
			multi_bs[i].bs.mv_buf_len = 0;
		}
		if ((ret = gm_recv_multi_bitstreams(multi_bs, MAX_V_BITSTREAM_NUM)) < 0) {
			LOG4CPLUS_ERROR(log_dsw, "Error return value! value = " << ret);
		}
		else {
			for (i = 0; i < MAX_V_BITSTREAM_NUM; i++) {
				if ((multi_bs[i].retval < 0) && multi_bs[i].bindfd) {
					LOG4CPLUS_ERROR(log_dsw, "CH[" << m_nChId << "] streams [" << i << "] Error to receive bitstream. ret = " << multi_bs[i].retval);
				}
				else if (multi_bs[i].retval == GM_SUCCESS) {
#if 0
//					if ((i == 0) && (m_nChId == 0))
						printf("<CH%d.%d, mv_len=%d bs_len=%d, keyframe=%d, newbsflag=0x%x, timestamp = %d>\n", 
							m_nChId, i, multi_bs[i].bs.mv_len, multi_bs[i].bs.bs_len,
							multi_bs[i].bs.keyframe, multi_bs[i].bs.newbs_flag, multi_bs[i].bs.timestamp);
#endif					
					PutFrameToMem(i, &(multi_bs[i].bs));
				}
			}
		}
	}
	for (i = 0; i < MAX_V_BITSTREAM_NUM; i++){
		if (bitstream_data[i] != NULL)
			free(bitstream_data[i]);
	}

	if (nCapturePicThread)
		pthread_join(nCapturePicThread, NULL);

	LOG4CPLUS_DEBUG(log_dsw, "Exit Ch[ " << m_nChId << " ]Video encode thread!");
}

void CVideo_svr::PutFrameToMem(int nStreamID, gm_enc_bitstream_t *pBs)
{
	pthread_mutex_lock(&m_video_mem[nStreamID].lock);
	LPST_VIDEO_FRAME_T pNode;
	if (m_video_mem[nStreamID].vlist.size() < MAX_FRAMES){
		pNode = new ST_VIDEO_FRAME_T;
		pNode->pFrame = new unsigned char[pBs->bs_len];
	}
	else{
		pNode = m_video_mem[nStreamID].vlist.front();
		m_video_mem[nStreamID].vlist.pop_front();
		if (pNode->length < pBs->bs_len){
			delete pNode->pFrame;
			pNode->pFrame = new unsigned char[pBs->bs_len];
		}
	}
	pNode->ch_num = m_nChId;
	pNode->sub_num = nStreamID;
	pNode->is_newbs = (pBs->newbs_flag != 0);
	pNode->is_keyframe = (pBs->keyframe == 1);
	pNode->seq = m_video_mem[nStreamID].seq++;
	pNode->length = pBs->bs_len;
	memcpy(pNode->pFrame, pBs->bs_buf, pBs->bs_len);
	gettimeofday(&pNode->timestamp, NULL);
	m_video_mem[nStreamID].vlist.push_back(pNode);
	pthread_mutex_unlock(&m_video_mem[nStreamID].lock);
}

void CVideo_svr::GetFrameFromMem(LPST_VIDEO_FRAME_T pFrame)
{
	bool bHaveFrame = false;
	int nstream = pFrame->sub_num;
	unsigned int nseq = pFrame->seq;
	pthread_mutex_t* pmutex = &(m_video_mem[nstream].lock);
	LIST_VIDEO_MEM_T* plist = &(m_video_mem[nstream].vlist);

	pFrame->length = -1;
	pthread_mutex_lock(pmutex);
	if (plist->empty()){
		pthread_mutex_unlock(pmutex);
		return;
	}
	LIST_VIDEO_MEM_T::iterator it;
	for (it = plist->begin(); it != plist->end(); it++){
		if ((*it)->seq >= nseq){
			if ((nseq == 0) && !(*it)->is_keyframe){
				continue;
			}
			if ((*it)->length > pFrame->length){
				if (pFrame->pFrame != NULL)
					delete pFrame->pFrame;
				pFrame->pFrame = new unsigned char[(*it)->length];
			}
			memcpy(pFrame, (*it), sizeof(ST_VIDEO_FRAME_T) - sizeof(unsigned char*));
			memcpy(pFrame->pFrame, (*it)->pFrame, (*it)->length);
			bHaveFrame = true;
			break;
		}
	}
	if ((nseq == 0) && !bHaveFrame)
		gm_request_keyframe((nstream == MAIN_STREAM) ? m_main_bindfd : m_sub_bindfd);
	pthread_mutex_unlock(pmutex);
}

void CVideo_svr::SetCapturePIC(unsigned int nAreId, unsigned int nTrigVal, time_t nTrigTime)
{
	pthread_mutex_lock(&m_alarm_list.nLock);
	if (m_alarm_list.nAlarmList.size() < MAX_ALARM_PROCESS_NODE){
		LPST_ALARM_MSG_T pNode = new ST_ALARM_MSG_T;
		pNode->nAreId = nAreId;
		pNode->nTrigVal = nTrigVal;
		pNode->nAlarmTime = nTrigTime;
		m_alarm_list.nAlarmList.push_back(pNode);
	}
	else
		LOG4CPLUS_WARN(log_dsw, "Ch [ " << m_nChId << " ] too many alarm to process! drop this pic cap alarm!");
	pthread_mutex_unlock(&m_alarm_list.nLock);
}

void CVideo_svr::CapturePICThreadFunc()
{
	LOG4CPLUS_DEBUG(log_dsw, "Begin Ch[ " << m_nChId << " ] pic capture thread!");
	while(!m_pEnv->bExit){
		pthread_mutex_lock(&m_alarm_list.nLock);
		if (m_alarm_list.nAlarmList.empty()){
			pthread_mutex_unlock(&m_alarm_list.nLock);
			usleep(1000 * 10);
			continue;
		}
		LPST_ALARM_MSG_T pNode = m_alarm_list.nAlarmList.front();
		m_alarm_list.nAlarmList.pop_front();
	
		snapshot_t snapshot;
		char* snapshot_buf = new char[MAX_SNAPHSOT_LEN];
		snapshot.bindfd = m_main_bindfd;
		snapshot.image_quality = 70;
		snapshot.bs_buf = snapshot_buf;
		snapshot.bs_buf_len = MAX_SNAPHSOT_LEN;
		snapshot.bs_width = MAKE_V_WIDTH(m_pChParam->vtype, m_pChParam->bs[MAIN_STREAM].size);
		snapshot.bs_height =  MAKE_V_HEIGHT(m_pChParam->vtype, m_pChParam->bs[MAIN_STREAM].size);
		int snapshot_len = gm_request_snapshot(&snapshot, 500);
		if (snapshot_len > 0){
			ST_CAPTURE_RESULT_T nResult;
			nResult.nAlarmTime = pNode->nAlarmTime;
			nResult.nAreId = pNode->nAreId;
			nResult.nPayloadType = EN_PIC;
			nResult.nPayloadSize = snapshot_len;
			nResult.nTrigVal = pNode->nTrigVal;
			nResult.pPic = snapshot_buf;
			nResult.nBindV = m_nChId;
			void* pParam = (void*)&nResult;
			m_pEnv->pOnNotifyFunc(PMSG_SET_CAPTURE_RESULT, pParam);
			LOG4CPLUS_DEBUG(log_dsw, "Ch[ " << m_nChId << " ] capture pic ok!");
		}
		else{
			LOG4CPLUS_ERROR(log_dsw, "Ch[ " << m_nChId << " ] capture pic failed!");
			delete snapshot_buf;
		}

		delete pNode;
		pthread_mutex_unlock(&m_alarm_list.nLock);
	}
	LOG4CPLUS_DEBUG(log_dsw, "Exit Ch[ " << m_nChId << " ] pic capture thread!");
}


